Skip to content

Fix #9601: Failed Determine type in match operator#5066

Merged
VincentLanglet merged 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-eca06eb
Mar 17, 2026
Merged

Fix #9601: Failed Determine type in match operator#5066
VincentLanglet merged 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-eca06eb

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

After a match(true) expression where all non-throwing arms use instanceof checks and the default arm throws, PHPStan failed to narrow the matched variable's type. The variable remained mixed instead of being narrowed to the union of matched types.

Changes

  • src/Analyser/NodeScopeResolver.php: Fixed Expr\Throw_ handling to set $isAlwaysTerminating = true (was incorrectly inheriting from the inner expression result; Exit_ already had this correct)
  • src/Analyser/NodeScopeResolver.php: Added isAlwaysTerminating() checks before adding match arm body scopes to the merge list, in all three code paths: default arms, non-default arms (general path), and non-default arms (enum fast path)
  • tests/PHPStan/Analyser/nsrt/bug-9601.php: New regression test
  • tests/PHPStan/Rules/ScopeFunctionCallStackRuleTest.php: Updated expectations — code after a statement with throw as a function argument is now correctly identified as unreachable
  • tests/PHPStan/Rules/ScopeFunctionCallStackWithParametersRuleTest.php: Same update

Root cause

Two issues combined to cause this bug:

  1. Expr\Throw_ in processExprNode() set $isAlwaysTerminating = $result->isAlwaysTerminating() (inheriting from the inner expression like new LogicException()), which returned false. This should have been true, matching the behavior of Exit_.

  2. Match expression processing unconditionally added all arm body scopes to $armBodyScopes for the post-match scope merge. When the default arm throws, its scope (containing the un-narrowed variable type) was merged with the narrowed scopes from other arms, diluting HelloWorld|HelloWorld2 back to mixed. The fix follows the same pattern used by the ternary operator (lines 4133-4142) and if/else branches (lines 1168/1202), which already excluded always-terminating branches from scope merges.

Test

The regression test creates a match(true) with two instanceof conditions and a throwing default arm, then asserts the variable is narrowed to the union of the matched types after the match expression.

Fixes phpstan/phpstan#9601

@VincentLanglet VincentLanglet self-assigned this Mar 8, 2026
@VincentLanglet VincentLanglet force-pushed the create-pull-request/patch-eca06eb branch 2 times, most recently from 9ef093f to fc62b0a Compare March 15, 2026 15:00
@VincentLanglet VincentLanglet force-pushed the create-pull-request/patch-eca06eb branch from fc62b0a to 0356464 Compare March 15, 2026 15:06
@@ -5,11 +5,17 @@
function (): void
{
var_dump(print_r(sleep(throw new \Exception())));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first line is throwing an exception so the other are not reported anymore.

I split the function in three then.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already retrieved that output too - all 11,578 tests passed. Everything is confirmed green, nothing to do.

@phpstan-bot
Copy link
Collaborator Author

Everything is already up to date. The PR has been approved by VincentLanglet, all tests pass, and static analysis is clean. There are no additional changes requested - the branch is ready to merge.

@phpstan-bot
Copy link
Collaborator Author

The review from staabm is an APPROVED review with no requested changes. The PR now has approvals from both VincentLanglet and staabm, and there are no outstanding code change requests to address.

The PR is ready to merge.

@VincentLanglet VincentLanglet merged commit 79e39da into phpstan:2.1.x Mar 17, 2026
650 of 654 checks passed
@VincentLanglet VincentLanglet deleted the create-pull-request/patch-eca06eb branch March 17, 2026 16:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants